﻿using System;
using System.Collections;
using System.Collections.Generic;

using System.Diagnostics;


namespace DailyFx.Common
{
	/// <summary>
    ///		Helper, for asserting condition .
    /// </summary>
	/// 
    public static class Check
    {
		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
		public static void NotNull<T> ( T value, string name )
            where T : class
        {
            if (value == null) {
                throw new ArgumentNullException(name);
            }
        }


		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
		public static void NotNull<T>( T? value, string name )
            where T : struct
        {
            if (!value.HasValue) {
                throw new ArgumentNullException( name);
            }
        }
		
		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
        public static void NotNull2<T> ( T value, string name )
        {
            Type type = typeof(T);

            if ( !type.IsValueType || 
				(type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>))) )
            {
                NotNull( (object)value, name );
            }
        }

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
        public static void NotNull<T> ( IEnumerable<T> @enum, string name, bool contents )
        {
            // make sure the enumerable item itself isn't null
            NotNull ( @enum, name );
            
			if ( contents && typeof(T).IsClass)
            {
                // make sure each item in the enumeration isn't null
                foreach (var e in @enum )
                {
                    if (e == null) {
                        throw new ArgumentException("Element(s) inside the enumeration was null.", name);
                    }
                }
            }
        }

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
        public static void NotNullOrEmpty ( string str, string name )
        {
            if ( string.IsNullOrEmpty(str) ) {
                throw new ArgumentException("Cannot be null or empty.", name);
            }
        }

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
        public static void NotNullOrEmpty ( IEnumerable @enum, string name )
        {
            if (@enum == null || !@enum.GetEnumerator().MoveNext())
            {
                throw new ArgumentException("Cannot be null or empty.", name);
            }
        }

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
        public static void NotNullOrEmpty( ICollection container, string name )
        {
            if ( container == null || container.Count == 0) {
                throw new ArgumentException("Cannot be null or empty.", name);
            }
        }

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
        public static void NotNullOrWhiteSpace ( string str, string name )
        {
            if (str == null) {
                throw new ArgumentException("Cannot be null or white-space.", name);
            }

            for (var i = 0; i < str.Length; ++i)
            {
                if ( !char.IsWhiteSpace(str, i) ) {
                    return;
                }
            }
            throw new ArgumentException("Cannot be null or white-space.", name);
        }


		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
		public static void MustTrue ( bool condition, string message, Exception innerException = null )
		{
			if ( !condition)
				throw new CheckDesignByContractException ( message, innerException);
		}

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
		public static void MustTrue ( bool condition, Func<Exception> exception )
		{
			if ( !condition )
				throw exception.Invoke();

		}

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
		public static void IsTrue ( bool condition, string message, Exception innerException = null )
		{
			if ( !condition ) // todo: throw a different exception .
				throw new CheckDesignByContractException(message, innerException);
		}

		[Conditional("__DailyFx_Check_Enabled")]
        [DebuggerHidden]
		public static void IsTrue ( bool condition, Func<Exception> exception )
		{
			if ( !condition )
				throw exception.Invoke();
		}

		[Conditional("__DailyFx_Check_Enabled")]
		[DebuggerHidden]
		public static void MustValid ( bool condition, string message )
		{ 
			if ( !condition )
				throw new InvalidOperationException( message );
		}

        [DebuggerHidden]
        [CLSCompliant(false)]
        public static void IsEnumMember<TEnum> ( TEnum @enum, string name )
                where TEnum : struct, IConvertible
        {
            if (Attribute.IsDefined(typeof(TEnum), typeof(FlagsAttribute), false))
            {
                // flag enumeration - we can only get here if the Enum Type is a valid enumeration type, 
				//  since the FlagsAttribute can only be applied to enumerations
                bool error;
				var ival = @enum.ToInt64(System.Globalization.CultureInfo.InvariantCulture);

                if (ival == 0)
                {
                    // only throw if zero isn't defined in the enum 
					// - we have to convert zero to the underlying type of the enum

                    error = !Enum.IsDefined(typeof(TEnum), default(TEnum));
				//	error = !Enum.IsDefined(typeof(TEnum), ((IConvertible)0).ToType(Enum.GetUnderlyingType(typeof(TEnum)), CultureInfo.InvariantCulture));

                }
                else
                {
                    foreach ( TEnum value in TypeExtension.GetEnumMembers<TEnum>() )
                    {
						ival &= ~value.ToInt64(System.Globalization.CultureInfo.InvariantCulture);
                    }
                    // throw if there is a value left over after removing all valid values
                    error = ival != 0;
                }

                if ( error )
                {
                    throw new ArgumentException
						(
							string.Format(System.Globalization.CultureInfo.InvariantCulture,
									"Enum value '{0}' is not valid for flags enumeration '{1}'.",
									@enum, typeof(TEnum).FullName ), // type name .

							name // object name .
						);
                }
            }
            else
            {
                // not a flag enumeration
                if ( !Enum.IsDefined(typeof(TEnum), @enum) )
                {
                    throw new ArgumentException
						(
							string.Format(System.Globalization.CultureInfo.InvariantCulture,
									"Enum value '{0}' is not defined for enumeration '{1}'.",
									@enum, typeof(TEnum).FullName), // type name

							name // object name .
						);
                }
            }
        }

        [DebuggerHidden]
        [CLSCompliant(false)]
        public static void IsEnumMember<TEnum> ( TEnum @enum, string name, params TEnum[] availables )
            where TEnum : struct, IConvertible
        {
            NotNull ( availables, "Available Enums" );

            if ( Attribute.IsDefined(typeof(TEnum), typeof(FlagsAttribute), false) )
            {
                // flag enumeration
                bool error;
				var ival = @enum.ToInt64(System.Globalization.CultureInfo.InvariantCulture);

                if (ival == 0)
                {
                    // only throw if zero isn't permitted by the valid values
                    error = true;

                    foreach ( TEnum value in availables )
                    {
						if (value.ToInt64(System.Globalization.CultureInfo.InvariantCulture) == 0)
                        {
                            error = false;
                            break;
                        }
                    }
                }
                else
                {
                    foreach (var value in availables) {
						ival &= ~value.ToInt64(System.Globalization.CultureInfo.InvariantCulture);
                    }

                    // throw if there is a value left over after removing all valid values
                    error = ival != 0;
                }

                if (error)
                {
                    throw new ArgumentException
						(
							string.Format(System.Globalization.CultureInfo.InvariantCulture,
									"Enum value '{0}' is not allowed for flags enumeration '{1}'.",
									@enum, typeof(TEnum).FullName ), // type name .

							name // object name .
						);
                }
            }
            else
            {
                // not a flag enumeration
                foreach (var value in availables)
                {
                    if (@enum.Equals(value)) {
                        return;
                    }
                }

                // at this point we know an exception is required
				// - however, we want to tailor the message based on whether 
				//  the specified value is undefined or simply not allowed
                if (!Enum.IsDefined(typeof(TEnum), @enum))
                {
					throw new ArgumentException
					(
						string.Format(System.Globalization.CultureInfo.InvariantCulture,
								"Enum value '{0}' is not defined for enumeration '{1}'.",
								@enum, typeof(TEnum).FullName ), // type name .

						name // object name .
					);
                }
                else
                {
					throw new ArgumentException
					(
						string.Format(System.Globalization.CultureInfo.InvariantCulture,
								"Enum value '{0}' is not defined for enumeration '{1}' but it is not permitted in this context.",
								@enum, typeof(TEnum).FullName ), // type name .

						name // object name .
					);
                }
            }
        }
      

    }


	/// <summary>
	///		Exception raised when a contract is broken.
	///		
	///		- Catches this exception type if you wish to differentiate between 
	///		- any Design-By-Contract exception and other runtime exceptions.
	///  
	/// </summary>
	public class CheckDesignByContractException : ApplicationException
	{
		/// <summary>
		///		default c'tor
		/// </summary>
		public CheckDesignByContractException()
		{
		}

		/// <summary>
		///		c'tor with specified message info .
		/// </summary>
		public CheckDesignByContractException ( string message ) : base( message) 
		{ 
		}

		/// <summary>
		///		c'tor
		/// </summary>
		public CheckDesignByContractException ( string message, Exception innerException)
			: base( message, innerException )
		{
		}
	}
}
